package gameoflife;

import gameoflife.automaton.Automaton;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class RunManager implements Runnable {

	public enum State {
		STOPPED,
		RUNNING;
	}

	private State state = State.STOPPED;

	private static final int INITIAL_SPEED = -4;
	private static final int MIN_SPEED = -8;
	private static final int MAX_SPEED = 5;

	private int speed = INITIAL_SPEED;

	private Automaton automaton;
	private Thread thread;
	private Pattern pattern;
	private List<ChangeListener> listeners = new ArrayList<ChangeListener>();

	public RunManager(Automaton automaton) {
		this.automaton = automaton;
	}

	public Automaton getAutomaton() {
		return automaton;
	}

	public void toggle() {
		switch (state) {
		case STOPPED:
			if (automaton.size() == 0) {
				break;
			}
			setState(State.RUNNING);
			if (pattern == null) {
				pattern = automaton.toPattern();
			}
			thread = new Thread(this);
			thread.start();
			break;

		case RUNNING:
			setState(State.STOPPED);
			try {
				thread.join();
			} catch (InterruptedException e) {
			}
			break;
		}
	}

	public void step() {
		step(getSteps());
	}

	public void step(int steps) {
		if (state == State.RUNNING) {
			state = State.STOPPED;
			stateChanged();
		}
		else {
			automaton.step(steps);
		}
	}

	@Override
	public void run() {
		resetTime();
		while (state == State.RUNNING) {

			long time = System.currentTimeMillis();
			automaton.step(getSteps());
			recordTime((System.currentTimeMillis() - time) / getSteps());

			if (automaton.size() == 0) {
				setState(State.STOPPED);
			}

			stateChanged();

			try {
				Thread.sleep(getSleep());
			} catch (InterruptedException e) {
			}
		}
	}

	private final static int MAX_WINDOW_SIZE = 20;
	private long windowSum;
	private Queue<Long> movingWindow = new LinkedList<Long>();

	private void resetTime() {
		windowSum = 0;
		movingWindow.clear();
	}

	private void recordTime(long time) {
		movingWindow.add(time);
		windowSum += time;
		if (movingWindow.size() > MAX_WINDOW_SIZE) {
			windowSum -= movingWindow.poll();
		}
	}

	public long getMovingAverage() {
		int size = movingWindow.size();
		return windowSum / (size == 0 ? 1 : size);
	}

	public State getState() {
		return state;
	}

	private final int getSleep() {
		// always sleep at least one milliseconds
		return speed < 0 ? (1 << -speed) : 1;
	}

	private final int getSteps() {
		return speed < 0 ? 1 : (1 << speed);
	}

	public void speedUp() {
		if (speed < MAX_SPEED) {
			speed++;
			stateChanged();
		}
	}

	public void speedDown() {
		if (speed > MIN_SPEED) {
			speed--;
			stateChanged();
		}
	}

	public String getSpeed() {
		if (speed < 0) {
			return "delay: " + (getSleep()) + "ms";
		}
		else {
			return "steps: " + getSteps();
		}
	}

	public Pattern getPattern() {
		return pattern;
	}

	public void setPattern(Pattern pattern) {
		this.pattern = pattern;
	}

	public void addChangeListener(ChangeListener listener) {
		listeners.add(listener);
	}

	private void setState(State state) {
		this.state = state;
		stateChanged();
	}

	private void stateChanged() {
		for (ChangeListener listener : listeners) {
			listener.stateChanged(0);
		}
	}
}
